//
//  TreeNode.h
//  RandomForest-AndreaBalboni
//
//  Created by Andrea Balboni on 05/04/12.
//  Copyright (c) 2012 Università degli Studi di Modena e Reggio Emilia. All rights reserved.
//

#ifndef RandomForest_AndreaBalboni_TreeNode_h
#define RandomForest_AndreaBalboni_TreeNode_h

#include <vector>
#include <map>
#include "TrainingData.h"
#include <opencv2/opencv.hpp>
template <typename _T>
class TreeNode {
private:
    std::vector<unsigned int> _trainingRecordIndexes;
	unsigned int _splitFeatureIndex;

    _T _threshold;
    bool _isLeaf;
    cv::Mat_<double> _w;     //direzione per la proiezione
    cv::Mat_<double> _meansAverage; //media delle medie delle classi
        
    TreeNode<_T> *_leftChild;
    TreeNode<_T> *_rightChild;
    
    TreeNode(const TreeNode<_T> &treeNode) {}
    
public:
    TreeNode() : _trainingRecordIndexes(0), _threshold(-1), _isLeaf(false), _leftChild(0), _rightChild(0) {
        
	}
    ~TreeNode(){
        delete [] _leftChild;
        delete [] _rightChild;
    }
    //Imposta la soglia di split
    void setThreshold(_T threshold){
        _threshold=threshold;
    }
    
    //Restituisce la soglia di split
	_T getThreshold(){
        return _threshold;
    }
    
    //Aggiunge un indice a _trainingRecordIndexes
    void pushRecordIndex(unsigned int i){
        _trainingRecordIndexes.push_back(i);
    }
    
    //Svuota i _trainingRecordIndexes
    void clearTrainingRecordsIndexes(){
        _trainingRecordIndexes.clear();
    }
    
    //Restituisce una reference a _trainingRecordIndexes
    std::vector<unsigned int>& getTrainingRecordIndexes(){
        return _trainingRecordIndexes;
    }
    
    //Imposta i trainingRecordIndexes da un altro vettore di indici casuali (Non usato per ora)
    void setTrainingRecordIndexes(std::vector<int>& randIndexes){
        _trainingRecordIndexes.clear();
        for(std::vector<int>::iterator it=randIndexes.begin();it!=randIndexes.end();++it){
            pushRecordIndex(*it);
        }
    }
    
    //Restituisce la dimensione di _trainingRecordIndexes
    unsigned int getTrainingRecordIndexesSize(){
        return _trainingRecordIndexes.size();
    }
    
    //Restituisce un indice di training record dato un indice
    void trainingRecordIndexAt(unsigned int recordIndex) {
        return this->_trainingRecordIndexes.at(recordIndex);
    }
    
    //Restituisce true se il nodo e' foglia
    bool isLeaf() {
        return _isLeaf;
    }
    
    //Imposta se il nodo e' una foglia
    void setIsLeaf(bool value) {
        _isLeaf = value;
    }
   
    //Imposta il nodo figlio a sinistra
    void setLeftChild(TreeNode *leftChild){
        _leftChild=leftChild;
    }
    
    //Imposta il nodo figlio a destra
    void setRightChild(TreeNode * rightChild){
        _rightChild=rightChild;
    }
    
    //Imposta l'indice della feature da usare per lo split
    void setSplitFeatureIndex(unsigned int featureindex){
        _splitFeatureIndex=featureindex;
    }
    
    //Restituisce l'indice della feature che viene usata per lo split
    int getSplitFeatureIndex(){
        return _splitFeatureIndex;
    }
    
    //Restituisce un puntatore al figlio di destra
    TreeNode<_T>* rightChild(){
        return _rightChild;
    }
    
    //Restituisce un puntatore al figlio di sinistra
    TreeNode<_T>* leftChild(){
        return _leftChild;
    }
    
    void setDirection(cv::Mat_<double> &w){
        w.copyTo(_w);
    }
    void getDirection(cv::Mat_<double> &w){
        _w.copyTo(w);
    }
    void setMeansAverage(cv::Mat_<double> &meansAverage){
        meansAverage.copyTo(_meansAverage);
    }
    void getMeansAverage(cv::Mat_<double> &meansAverage){
        _meansAverage.copyTo(meansAverage);
    }
    //Imposta in un vettore passato per reference gli indici delle feature che dovranno andare nel nodo destro o sininstro dopo lo split
    void getFilteredRecords(unsigned int featureIndex,
							_T threshold,
							unsigned char LEFT_RIGHT_CHILD,
							TrainingData<_T> &DataSet,
							std::vector<unsigned int>& outputFilteredRecords){
        
        if(LEFT_RIGHT_CHILD == 0) { //Figlio a sinistra
            std::vector<unsigned int>& trainingRecords = this->getTrainingRecordIndexes();
            std::vector<unsigned int>::iterator it = trainingRecords.begin();
            
            for(; it != trainingRecords.end(); it++) {
                /* Scorro gli indici e verifico quelli che corrispondono a record di 
                 training che in corrispondenza di una determinata dimensione hanno un valore
                 <= a quello di soglia */
                if(DataSet.getRecord(*it)(featureIndex) <= threshold)
                    outputFilteredRecords.push_back(*it);
                
            }
        } else if(LEFT_RIGHT_CHILD == 1) { //Figlio a destra
            
            std::vector<unsigned int>& trainingRecords = this->getTrainingRecordIndexes();
            std::vector<unsigned int>::iterator it = trainingRecords.begin();
            
            for(; it != trainingRecords.end(); it++) {
                /* Scorro gli indici e verifico quelli che corrispondono a record di 
                 training che in corrispondenza di una determinata dimensione hanno un valore
                 <= a quello di soglia */
                if(DataSet.getRecord(*it)(featureIndex) > threshold)
                    outputFilteredRecords.push_back(*it);
                
            }
        }
        
    }
    
    //Imposta in un vettore passato per reference gli indici delle feature che dovranno andare nel nodo destro o sininstro dopo lo split eseguendo la classificazione con fisher
    void getFisherFilteredRecords(  cv::Mat_<double> direction,
                                    cv::Mat_<double> meansAverage,
                                    unsigned char LEFT_RIGHT_CHILD,
                                    TrainingData<_T> &DataSet,
                                    std::vector<unsigned int>& outputFilteredRecords){
        
        if(LEFT_RIGHT_CHILD == 0) { //Figlio a sinistra
            std::vector<unsigned int>& trainingRecords = this->getTrainingRecordIndexes();
            std::vector<unsigned int>::iterator it = trainingRecords.begin();
            
            for(; it != trainingRecords.end(); it++) {
                /* Ottengo il vettore delle features del record in forma matriciale */
                cv::Mat_<double> x,res;
                DataSet.getRecordMat(*it,x);
                //Calcolo la classificazione
                res=direction.t()*x-direction.t()*(meansAverage);
                //estraggo il risultato della classificazione
                double r=res(0);
                //std::cout<<"res dim: "<<res.rows<<"x"<<res.cols<< " r "<<r<<std::endl;
                if( r<= 0)
                    outputFilteredRecords.push_back(*it);
                
            }
        } else if(LEFT_RIGHT_CHILD == 1) { //Figlio a destra
            
            std::vector<unsigned int>& trainingRecords = this->getTrainingRecordIndexes();
            std::vector<unsigned int>::iterator it = trainingRecords.begin();
            
            for(; it != trainingRecords.end(); it++) {
                /* Ottengo il vettore delle features in forma matriciale*/
                cv::Mat_<double> x,res;
                DataSet.getRecordMat(*it,x);
//                int c=0;
//                
//                std::cout<<x.rows<<"x"<<x.cols<<std::endl;
//                for(cv::Mat_<double>::iterator it=x.begin();it!=x.end();++it){
//                    std::cout<<*it<<"\t";
//                    c++;
//                    if(c==x.cols){
//                        std::cout<<std::endl;
//                        c=0;
//                    }
//                }
//                
                //Calcolo la classificazione
                res=direction.t()*x-direction.t()*(meansAverage);
                double r=res(0);
                //std::cout<<"res dim: "<<res.rows<<"x"<<res.cols<< " r "<<r<<std::endl;

                if( r> 0)
                //if((DataSet.getRecord(*it)(featureIndex)*direction)-(0.5*direction*(m1+m2)) > 0)
                    outputFilteredRecords.push_back(*it);
                
            }
        }
        
    }
    //Funzione che restituisce un vettore contenente gli indici dei record della classe specificata
    //e un vettore con gli indici dei record di tutte le altre classi
    
    void getClassFilteredRecords(int mainClass, TrainingData<_T> &DataSet, std::vector<unsigned int>& mainClassIndexes, std::vector<unsigned int>& otherClassesIndexes){
        std::vector<unsigned int>& trainingRecords=this->getTrainingRecordIndexes();
        std::vector<unsigned int>::iterator it=trainingRecords.begin();
        for(; it!=trainingRecords.end();++it){
            if(DataSet.getRecord(*it).getClass()==mainClass)
                mainClassIndexes.push_back(*it);
            else 
                otherClassesIndexes.push_back(*it);
            
        }
    }

    
};


#endif
